/**
 * 
 */
package adnotatio.renderer.templates;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import adnotatio.common.cache.CacheBuilder;
import adnotatio.common.cache.CacheNode;
import adnotatio.common.cache.CompositeCacheNode;
import adnotatio.common.cache.ICacheNodeVisitor;
import adnotatio.common.cache.TextCacheNode;

import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.Node;
import com.google.gwt.xml.client.Text;

/**
 * This is a registry of {@link IFieldInfoFactory} instances. It maps individual
 * "tag names" to the corresponding {@link IFieldInfoFactory} objects used to
 * create {@link FieldInfo} for real widgets.
 * 
 * @author kotelnikov
 */
public class TemplateBuilder implements ITemplateNamespaces {

    static class ElementCache extends CompositeCacheNode {

        public final Element element;

        public final IFieldInfoFactory factory;

        public ElementCache(IFieldInfoFactory factory, Element e) {
            this.factory = factory;
            this.element = e;
        }

    }

    static class PanelGeneratingVisitor extends FieldPanelGenerator
        implements
        ICacheNodeVisitor {

        public PanelGeneratingVisitor(FieldPanel panel) {
            super(panel);
        }

        public void visit(CompositeCacheNode node) {
            ElementCache e = (ElementCache) node;
            beginField(e.factory, e.element);
            try {
                int len = node.getChildrenCount();
                for (int i = 0; i < len; i++) {
                    CacheNode child = node.getChild(i);
                    child.accept(this);
                }
            } finally {
                endField();
            }
        }

        public void visit(TextCacheNode node) {
            addText(node.getText());
        }

    }

    CacheBuilder fBuilder = new CacheBuilder() {

        /**
         * @see adnotatio.common.cache.CacheBuilder#getChildren(java.lang.Object,
         *      CompositeCacheNode)
         */
        protected Iterator getChildren(
            Object node,
            CompositeCacheNode compositeNode) {
            if (!(node instanceof Element))
                return null;
            final Element e = (Element) node;
            return new Iterator() {

                Node node = e.getFirstChild();

                public boolean hasNext() {
                    return node != null;
                }

                public Object next() {
                    Node n = node;
                    node = node != null ? node.getNextSibling() : null;
                    return n;
                }

                public void remove() {
                    throw new RuntimeException();
                }

            };
        }

        /**
         * @see adnotatio.common.cache.CacheBuilder#getCompositeNode(java.lang.Object)
         */
        protected CompositeCacheNode getCompositeNode(Object object) {
            if (!(object instanceof Element))
                return null;
            Element e = (Element) object;
            String tagName = XMLUtil.getURL(e);
            Node typeNode = XMLUtil.getAttributeNodeNS(e, X_ATTR_TYPE);
            String type = typeNode != null ? typeNode.getNodeValue() : null;

            IFieldInfoFactory factory = fFieldInfoFactoryRegistry
                .getFieldInfoFactoryNS(tagName, type);
            CompositeCacheNode result = null;
            if (factory != null) {
                result = new ElementCache(factory, e);
            }
            return result;
        }

        /**
         * @see adnotatio.common.cache.CacheBuilder#getTextPrefix(java.lang.Object,
         *      CompositeCacheNode)
         */
        protected String getTextPrefix(
            Object node,
            CompositeCacheNode compositeNode) {
            if (compositeNode != null)
                return null;
            if (node instanceof Element) {
                StringBuffer buf = new StringBuffer();
                XMLUtil.serializeOpenTag((Element) node, buf);
                return buf.toString();
            } else if (node instanceof Text) {
                StringBuffer buf = new StringBuffer();
                XMLUtil.serializeText((Text) node, buf);
                return buf.toString();
            }
            return null;
        }

        /**
         * @see adnotatio.common.cache.CacheBuilder#getTextSuffix(java.lang.Object,
         *      CompositeCacheNode)
         */
        protected String getTextSuffix(
            Object node,
            CompositeCacheNode compositeNode) {
            if (compositeNode != null)
                return null;
            if (node instanceof Element) {
                StringBuffer buf = new StringBuffer();
                XMLUtil.serializeCloseTag((Element) node, buf);
                return buf.toString();
            }
            return null;
        }

        protected CompositeCacheNode newTopNode(Object object) {
            Element e = (Element) object;
            return new ElementCache(null, e);
        }

    };

    private IFieldI18N fConstants;

    FieldInfoFactoryRegistry fFieldInfoFactoryRegistry;

    private Map fTemplates = new HashMap();

    /**
     * @param registry
     */
    public TemplateBuilder(FieldInfoFactoryRegistry registry) {
        this(registry, IFieldI18N.NULL);
    }

    /**
     * @param registry
     */
    public TemplateBuilder(
        FieldInfoFactoryRegistry registry,
        IFieldI18N constants) {
        super();
        fFieldInfoFactoryRegistry = registry;
        fConstants = constants;
    }

    public void addTemplate(String templateName, Element templateNode) {
        CompositeCacheNode template = fBuilder.build(templateNode);
        fTemplates.put(templateName, template);
    }

    public FieldPanel buildPanel(String templateName) {
        return buildPanel(templateName, fConstants);
    }

    public FieldPanel buildPanel(String templateName, IFieldI18N constants) {
        CompositeCacheNode template = (CompositeCacheNode) fTemplates
            .get(templateName);
        if (template == null)
            return null;
        FieldPanel panel = new FieldPanel(constants);
        PanelGeneratingVisitor generator = new PanelGeneratingVisitor(panel);
        template.accept(generator);
        return panel;
    }

    public IFieldI18N getConstants() {
        return fConstants;
    }

    public void removeTemplate(String templateName) {
        fTemplates.remove(templateName);
    }

    public void setConstants(IFieldI18N constants) {
        fConstants = constants;
    }

}
